package org.zebra.web.config;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import javax.annotation.PreDestroy;
import java.util.Arrays;
import java.util.concurrent.*;

/**
 * 线程池配置
 *
 * @author zhanghongbin
 **/
@Slf4j
@Configuration
public class ThreadPoolConfig implements AsyncConfigurer {

    private ScheduledExecutorService scheduledExecutorService;

    /**
     * 核心（最小）线程数 = CPU 核心数 + 1
     */
    private final int corePoolSize = Runtime.getRuntime().availableProcessors() + 1;

    /**
     * Spring 内置线程池：ThreadPoolTaskExecutor
     *
     * @return 对象
     */
    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心（最小）线程数
        executor.setCorePoolSize(corePoolSize);
        // 最大线程数
        executor.setMaxPoolSize(corePoolSize * 2);
        // 队列容量
        executor.setQueueCapacity(128);
        // 活跃时间
        executor.setKeepAliveSeconds(300);
        // 配置当池内线程数已达到上限的时候，该如何处理新任务：不在新线程中执行任务，而是由调用者所在的线程来执行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    /**
     * Java 内置线程池：ScheduledExecutorService（适用于执行周期性或定时任务）
     * @return 对象
     */
    @Bean
    @ConditionalOnMissingBean
    public ScheduledExecutorService scheduledExecutorService() {
        scheduledExecutorService =
                new ScheduledThreadPoolExecutor(
                        corePoolSize,
                        ThreadUtil.newNamedThreadFactory("schedule-pool-%d", true),
                        new ThreadPoolExecutor.CallerRunsPolicy()) {
                    @Override
                    protected void afterExecute(Runnable runnable, Throwable throwable) {
                        super.afterExecute(runnable, throwable);
                        if (throwable != null) {
                            log.error(throwable.getMessage(), throwable);
                        }
                    }
                };
        return scheduledExecutorService;
    }

    /**
     * 异步任务 @Async 执行时，使用 Java 内置线程池
     * @return 对象
     */
    @Override
    public Executor getAsyncExecutor() {
        return scheduledExecutorService;
    }

    /**
     * 异步任务执行时的异常处理
     * @return 对象
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return (throwable, method, objects) -> {
            throwable.printStackTrace();
            StringBuilder sb = new StringBuilder();
            sb.append("Exception message: ")
                    .append(throwable.getMessage())
                    .append(", Method name: ")
                    .append(method.getName());
            if (ArrayUtil.isNotEmpty(objects)) {
                sb.append(", Parameter value: ").append(Arrays.toString(objects));
            }
            throw ExceptionUtil.wrapRuntime(sb.toString());
        };
    }

    /**
     * 销毁事件
     */
    @PreDestroy
    public void destroy() {
        try {
            if (scheduledExecutorService != null && !scheduledExecutorService.isShutdown()) {
                scheduledExecutorService.shutdown();
                try {
                    if (!scheduledExecutorService.awaitTermination(120, TimeUnit.SECONDS)) {
                        scheduledExecutorService.shutdownNow();
                        if (!scheduledExecutorService.awaitTermination(120, TimeUnit.SECONDS)) {}
                    }
                } catch (InterruptedException ie) {
                    scheduledExecutorService.shutdownNow();
                    Thread.currentThread().interrupt();
                }
            }
        } catch (Exception e) {

        }
    }
}
